//// Impl unicode EscapeBMP
#include "common.hpp"
#include <cstddef>
#include <cstdio>
#include <cstring>
#include <string>

#if defined(__cplusplus) && __cplusplus >= 201703L
#define FALLTHROUGH [[fallthrough]]
#else
#define FALLTHROUGH
#endif

#define UNI_REPLACEMENT_CHAR (char32_t)0x0000FFFD
#define UNI_MAX_BMP (char32_t)0x0000FFFF
#define UNI_MAX_UTF16 (char32_t)0x0010FFFF
#define UNI_MAX_UTF32 (char32_t)0x7FFFFFFF
#define UNI_MAX_LEGAL_UTF32 (char32_t)0x0010FFFF

#define UNI_MAX_UTF8_BYTES_PER_CODE_POINT 4

#define UNI_UTF16_BYTE_ORDER_MARK_NATIVE 0xFEFF
#define UNI_UTF16_BYTE_ORDER_MARK_SWAPPED 0xFFFE
#define UNI_SUR_HIGH_START (char32_t)0xD800
#define UNI_SUR_HIGH_END (char32_t)0xDBFF
#define UNI_SUR_LOW_START (char32_t)0xDC00
#define UNI_SUR_LOW_END (char32_t)0xDFFF

namespace gitee {

/*
 * Index into the table below with the first byte of a UTF-8 sequence to
 * get the number of trailing bytes that are supposed to follow it.
 * Note that *legal* UTF-8 values can't have 4 or 5-bytes. The table is
 * left as-is for anyone who may want to do such conversion, which was
 * allowed in earlier algorithms.
 */
// clang-format off
    static const char trailingbytesu8[256] = {
        0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
        0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
        0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
        0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
        0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
        0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
        1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
        2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5
    };
// clang-format on
static const char32_t offsetfromu8[6] = {0x00000000UL, 0x00003080UL,
                                         0x000E2080UL, 0x03C82080UL,
                                         0xFA082080UL, 0x82082080UL};
template <size_t N>
gitee::string_view EncodeUnicode(char (&buf)[N], char32_t ch) {
  char *end = buf + N;
  char *writer = end;
  uint64_t value = ch;
  static const char hexdigits[] = "0123456789ABCDEF";
  do {
    *--writer = static_cast<char>(hexdigits[value & 0xF]);
    value >>= 4;
  } while (value != 0);

  char *beg;
  if (end - writer < 8) {
    beg = end - 8;
    std::fill_n(beg, writer - beg, '0');
  } else {
    beg = writer;
  }
  return gitee::string_view(beg, end - beg);
}

// EscapeUnicode UTF-8
std::string EscapeUnicode(gitee::string_view sv) {
  if (sv.empty()) {
    return "";
  }
  std::string s;
  s.reserve(sv.size());
  char ub[10] = {0};
  auto len = sv.size();
  auto it = reinterpret_cast<const unsigned char *>(sv.data());
  auto end = it + len;
  while (it < end) {
    auto c = *it;
    if (c == '\\') {
      s += "\\\\";
      it++;
      continue;
    }
    if (c < 0x80) {
      s += c;
      it++;
      continue;
    }
    char32_t ch = 0;
    unsigned short nb = trailingbytesu8[*it];
    if (nb >= end - it) {
      return s;
    }
    auto cb = it;
    switch (nb) {
    case 5:
      ch += *it++;
      ch <<= 6; /* remember, illegal UTF-8 */
      FALLTHROUGH;
    case 4:
      ch += *it++;
      ch <<= 6; /* remember, illegal UTF-8 */
      FALLTHROUGH;
    case 3:
      ch += *it++;
      ch <<= 6;
      FALLTHROUGH;
    case 2:
      ch += *it++;
      ch <<= 6;
      FALLTHROUGH;
    case 1:
      ch += *it++;
      ch <<= 6;
      FALLTHROUGH;
    case 0:
      ch += *it++;
    }
    ch -= offsetfromu8[nb];
    if (ch <= UNI_MAX_BMP) {
      s.append(reinterpret_cast<const char *>(cb), nb + 1);
      continue;
    }
    auto sv = EncodeUnicode(ub, ch);
    s.append("\\U").append(sv.data(), sv.size());
  }
  return s;
}

inline int hex_digit_to_int(char c) {
  static_assert('0' == 0x30 && 'A' == 0x41 && 'a' == 0x61,
                "Character set must be ASCII.");
  int x = static_cast<unsigned char>(c);
  if (x > '9') {
    x += 9;
  }
  return x & 0xf;
}

inline bool IsSurrogate(char32_t c) {
  if (c >= 0xD800 && c <= 0xDFFF) {
    return true;
  }
  return false;
}

size_t EncodeUTF8Char(char *buffer, char32_t utf8_char) {
  if (utf8_char <= 0x7F) {
    *buffer = static_cast<char>(utf8_char);
    return 1;
  }
  if (utf8_char <= 0x7FF) {
    buffer[1] = 0x80 | (utf8_char & 0x3F);
    utf8_char >>= 6;
    buffer[0] = 0xC0 | utf8_char;
    return 2;
  }
  if (utf8_char <= 0xFFFF) {
    buffer[2] = 0x80 | (utf8_char & 0x3F);
    utf8_char >>= 6;
    buffer[1] = 0x80 | (utf8_char & 0x3F);
    utf8_char >>= 6;
    buffer[0] = 0xE0 | utf8_char;
    return 3;
  }
  buffer[3] = 0x80 | (utf8_char & 0x3F);
  utf8_char >>= 6;
  buffer[2] = 0x80 | (utf8_char & 0x3F);
  utf8_char >>= 6;
  buffer[1] = 0x80 | (utf8_char & 0x3F);
  utf8_char >>= 6;
  buffer[0] = 0xF0 | utf8_char;
  return 4;
}

bool UnescapeUnicode(gitee::string_view source, bool leave_nulls_escaped,
                     char *dest, std::ptrdiff_t *dest_len) {
  char *d = dest;
  const char *p = source.data();
  const char *end = p + source.size();
  const char *last_byte = end - 1;

  // Small optimization for case where source = dest and there's no escaping
  while (p == d && p < end && *p != '\\')
    p++, d++;

  while (p < end) {
    if (*p != '\\') {
      *d++ = *p++;
    } else {
      if (++p > last_byte) { // skip past the '\\'
        return false;
      }
      switch (*p) {
      case 'a':
        *d++ = '\a';
        break;
      case 'b':
        *d++ = '\b';
        break;
      case 'f':
        *d++ = '\f';
        break;
      case 'n':
        *d++ = '\n';
        break;
      case 'r':
        *d++ = '\r';
        break;
      case 't':
        *d++ = '\t';
        break;
      case 'v':
        *d++ = '\v';
        break;
      case '\\':
        *d++ = '\\';
        break;
      case '?':
        *d++ = '\?';
        break; // \?  Who knew?
      case '\'':
        *d++ = '\'';
        break;
      case '"':
        *d++ = '\"';
        break;
      case '0':
      case '1':
      case '2':
      case '3':
      case '4':
      case '5':
      case '6':
      case '7': {
        // octal digit: 1 to 3 digits
        const char *octal_start = p;
        unsigned int ch = *p - '0';
        if (p < last_byte && isdigit(p[1]))
          ch = ch * 8 + *++p - '0';
        if (p < last_byte && isdigit(p[1]))
          ch = ch * 8 + *++p - '0'; // now points at last digit
        if (ch > 0xff) {
          return false;
        }
        if ((ch == 0) && leave_nulls_escaped) {
          // Copy the escape sequence for the null character
          const std::ptrdiff_t octal_size = p + 1 - octal_start;
          *d++ = '\\';
          memcpy(d, octal_start, octal_size);
          d += octal_size;
          break;
        }
        *d++ = ch;
        break;
      }
      case 'x':
      case 'X': {
        if (p >= last_byte) {
          return false;
        }
        if (!isxdigit(p[1])) {
          return false;
        }
        unsigned int ch = 0;
        const char *hex_start = p;
        while (p < last_byte && isxdigit(p[1]))
          // Arbitrarily many hex digits
          ch = (ch << 4) + hex_digit_to_int(*++p);
        if (ch > 0xFF) {
          return false;
        }
        if ((ch == 0) && leave_nulls_escaped) {
          // Copy the escape sequence for the null character
          const std::ptrdiff_t hex_size = p + 1 - hex_start;
          *d++ = '\\';
          memcpy(d, hex_start, hex_size);
          d += hex_size;
          break;
        }
        *d++ = ch;
        break;
      }
      case 'u': {
        // \uhhhh => convert 4 hex digits to UTF-8
        char32_t rune = 0;
        const char *hex_start = p;
        if (p + 4 >= end) {
          return false;
        }
        for (int i = 0; i < 4; ++i) {
          // Look one char ahead.
          if (hex_digit_to_int(p[1])) {
            rune = (rune << 4) + hex_digit_to_int(*++p); // Advance p.
          } else {
            return false;
          }
        }
        if ((rune == 0) && leave_nulls_escaped) {
          // Copy the escape sequence for the null character
          *d++ = '\\';
          memcpy(d, hex_start, 5); // u0000
          d += 5;
          break;
        }
        if (IsSurrogate(rune)) {
          return false;
        }
        d += EncodeUTF8Char(d, rune);
        break;
      }
      case 'U': {
        // \Uhhhhhhhh => convert 8 hex digits to UTF-8
        char32_t rune = 0;
        const char *hex_start = p;
        if (p + 8 >= end) {
          return false;
        }
        for (int i = 0; i < 8; ++i) {
          // Look one char ahead.
          if (isxdigit(p[1])) {
            // Don't change rune until we're sure this
            // is within the Unicode limit, but do advance p.
            uint32_t newrune = (rune << 4) + hex_digit_to_int(*++p);
            if (newrune > 0x10FFFF) {
              return false;
            } else {
              rune = newrune;
            }
          } else {
            return false;
          }
        }
        if ((rune == 0) && leave_nulls_escaped) {
          // Copy the escape sequence for the null character
          *d++ = '\\';
          memcpy(d, hex_start, 9); // U00000000
          d += 9;
          break;
        }
        if (IsSurrogate(rune)) {
          return false;
        }
        d += EncodeUTF8Char(d, rune);
        break;
      }
      default: {
        return false;
      }
      }
      p++; // read past letter we escaped
    }
  }
  *dest_len = d - dest;
  return true;
}

// ----------------------------------------------------------------------
// UnescapeUnicode()
//
//    Same as above but uses a std::string for output. 'source' and 'dest'
//    may be the same.
// ----------------------------------------------------------------------
bool UnescapeUnicode(gitee::string_view source, bool leave_nulls_escaped,
                     std::string *dest) {
  dest->resize(source.size());

  std::ptrdiff_t dest_size = 0;
  if (!UnescapeUnicode(source, leave_nulls_escaped, &(*dest)[0], &dest_size)) {
    return false;
  }
  dest->erase(dest_size);
  return true;
}

//
} // namespace gitee
